Repository navigation
feat(processes): start processes on events in the same transaction, with tasks and processes in core - #227
Merged
artem-broccoli merged 8 commits intoNov 15, 2026
Conversation
…ches, keeping each run's own event
…its for a task change
…est seed under its line limit
…ug into (#233) ## Summary Tasks and processes become shared foundations in `core/`, beside records, and import no business module. Features plug in instead: - **Record types** register what a process needs from their records: who a record is about (`readSubjects`) and the dates a record-date trigger may start on (`dateFields`, `findByDateBetween`). Employees registers `employee` (its `startDate`); tasks registers `task`. - **Triggering events** are registered by the module that publishes them (`employees.added` by employees; `tasks.task_created` and `tasks.task_status_changed` by tasks), collected once at startup; a key registered twice stops startup. - **People and working days** are read through `PeoplePort` and `WorkingCalendarsPort`, which employees fills with adapters, together with the bank holiday rule. Ports live in a module's `ports/` folder and their implementations in the implementing module's `adapters/` folder, both enforced as lint layers. - **A process step's task is marked done with the ordinary `tasks.markDone`.** Processes listens to the task's status event before the commit and completes the step and the run in the same transaction. Every transaction that locks a task and its run takes the task first; the daily check now locks a run's open tasks before the run. A run that can no longer go on (its record or person is gone) is left for the daily check, and a task done meanwhile stays done. - Tasks no longer store which process step created them; `Task.processSource` is served by processes, and the Employee fields of `Task` by employees, so the GraphQL shape the web app reads stays the same. - **Tasks and runs are domain aggregates.** `Task` owns who sees it, who marks it done or deletes it, and that done and cancelled are final. `ProcessRun` holds its steps and owns completing them, finishing, the removal check and cancelling; changes load it with `lockRun` and write it back with `saveRun`. - **Process definitions:** a step keeps `key` and `kind` at its root and its kind's fields under it (`task: { assignee, title, description, due }`); `startableByAdmin` is gone, so an admin may start any process by hand with `create` on `process_run`. The run steps table is now `process_run_step_progress`: the steps live in the version's definition, the table holds a run's progress on each. - Counting working days moves to `utils` (`addWorkingDays`); processes keeps what a day offset means and its limit. - New rule RULE77: a `module.md` opens with JSON examples of its main flow; `processes/module.md` shows a run started by a change and by a date. Behaviour changes: - Done and cancelled tasks are final: Reopen, the Undo of Mark done and the reopen step command are gone. - People no longer cancel tasks. The person who asked can delete an open task they asked for (`tasks.delete`), with a confirmation; only a process cancels the tasks it created, with a reason. - Cancelling a run by hand is gone from the process service (it had no API or screen). Stacked on #227. ## Testing - `pnpm check`, all API integration tests (1546) and end-to-end tests (304) pass; web unit tests pass. - New tests: deleting a task (asker, assignee, unrelated caller, done, cancelled, process task), finishing steps through `tasks.markDone` (both steps, one step, concurrent completion, removed subject or assignee, manual tasks untouched), the daily check cancelling a run whose task record was deleted, definition parsing against registrations, and refusing a key registered twice. - The migrations are edited in place; `drizzle-kit generate` reports no change for tasks and processes. The Docker image builds and `pnpm dev` starts on a fresh database. - `docs/qa/tasks.md` is updated for Delete and the removed Reopen and Undo. - After the review changes: processes, tasks and employees integration tests (544) and processes unit tests pass on a fresh database; typecheck and lint pass. - Not done: screenshots of the Delete menu and dialog. Release Notes: - Improved: marking a process task done now works from Tasks like any other task. - Changed: a task you asked for can be deleted while it is open; done tasks can no longer be reopened and tasks can no longer be cancelled by hand.
artem-broccoli
deleted the
artem/bro-262-never-lose-a-record-change-that-starts-a
branch
November 15, 2026 06:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two changes to processes and tasks.
core/, beside records, and import no business module. Features plug into them.Processes start on domain events in the same transaction
Until now, processes started only on a record's date, through the daily job. Event triggers were refused because events were dispatched in memory after the commit, and a restart lost them.
{ "kind": "on_event", "event": "employees.added", "when": { "field": "to", "in": ["done", "cancelled"] }, "run": "in_same_transaction", "faultMode": "rollback_transaction" }.whenfilters on one payload field.runandfaultModeare required, so that running after the commit and failing quietly can be added later without breaking definitions.tasks.task_createdandtasks.task_status_changed, withbyEmployeeId, the person who acted. A run keeps the event as it concerns its own record (process_runs.trigger_event), so a step can tell who made the change.FOR NO KEY UPDATE, which still makes the daily job, an admin's start, step commands and resets take turns without blocking a new run. A reset locks every task before it deletes employees, so it waits for an in-flight task change instead of deadlocking with it.Tasks and processes become core
readSubjects) and the dates a record-date trigger may start on (dateFields,findByDateBetween). Employees registersemployee(itsstartDate); tasks registerstask.employees.addedby employees;tasks.task_createdandtasks.task_status_changedby tasks) and collected once at startup. A key registered twice stops startup.PeoplePortandWorkingCalendarsPort, which employees fills with adapters, together with the bank holiday rule. Ports live in a module'sports/folder and their implementations in the implementing module'sadapters/folder; lint enforces both as layers.tasks.markDone. Processes listens to the task's status event before the commit and completes the step and the run in the same transaction. Every transaction that locks a task and its run takes the task first; the daily check now locks a run's open tasks before the run. A run that can no longer go on (its record or person is gone) is left for the daily check, and a task done meanwhile stays done.Task.processSourceand employees serves the employee fields ofTask, so the GraphQL shape the web app reads stays the same.Taskowns who sees it, who marks it done or deletes it, and that done and cancelled are final.ProcessRunholds its steps and owns completing them, finishing, the removal check and cancelling. Changes load it withlockRunand write it back withsaveRun.keyandkindat its root and its kind's fields under it (task: { assignee, title, description, due }).startableByAdminis gone, so an admin may start any process by hand withcreateonprocess_run. The run steps table is nowprocess_run_step_progress: the steps live in the version's definition, and the table holds a run's progress on each.utils(addWorkingDays); processes keeps what a day offset means and its limit.module.mdopens with JSON examples of its main flow.processes/module.mdshows a run started by a change and by a date.Behaviour changes
tasks.delete), after a confirmation. Only a process cancels the tasks it created, with a reason.Testing
whenthat does not match, starts nothing.tasks.markDone: both steps, one step, concurrent completion, a removed subject or assignee, and manual tasks left untouched.pnpm check, all API integration tests and all end-to-end tests pass; web unit tests pass.drizzle-kit generatereports no change for tasks and processes. The Docker image builds, andpnpm devstarts on a fresh database.docs/qa/tasks.mdis updated for Delete and for the removed Reopen and Undo.pnpm devand on the Docker Compose image:Release Notes: